#include <game/cgame.h>
#include <utils/cutils.h>
#include <utils/cconfig.h>
#include <physfs/physfs.h>
#include <GL/glew.h>
#include <GL/glfw.h>
#include <graphics/cgraphicsmanager.h>

/** Default Constructor
 */

CGame::CGame()
{

}

/** Default Destructor
 */

CGame::~CGame()
{

}

/** Default Copy Constructor
 */

CGame::CGame(const CGame& other)
{

}

/** Default Assignment Operator
 */

CGame& CGame::operator=(const CGame& rhs)
{
    if (this == &rhs)
    {
        return *this;
    }

    return *this;
}

/** Executes the game, from initialization to shutdown.
 *  \param Number of arguments in argv. Only passed here, so it can be
 *  passed into Initialize.
 *  \param Pointer to C style strings (arguments passed into main). Only
 *  passed here, so it can be passed into Initialize
 */
#include <vector>
 GLuint LoadShaders(const char * vertex_file_path,const char * fragment_file_path){

    // Create the shaders
    GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
    GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);

    // Read the Vertex Shader code from the file
    std::string VertexShaderCode;
    std::ifstream VertexShaderStream(vertex_file_path, std::ios::in);
    if(VertexShaderStream.is_open())
    {
        std::string Line = "";
        while(getline(VertexShaderStream, Line))
            VertexShaderCode += "\n" + Line;
        VertexShaderStream.close();
    }

    // Read the Fragment Shader code from the file
    std::string FragmentShaderCode;
    std::ifstream FragmentShaderStream(fragment_file_path, std::ios::in);
    if(FragmentShaderStream.is_open()){
        std::string Line = "";
        while(getline(FragmentShaderStream, Line))
            FragmentShaderCode += "\n" + Line;
        FragmentShaderStream.close();
    }

    GLint Result = GL_FALSE;
    int InfoLogLength;

    // Compile Vertex Shader
    printf("Compiling shader : %s\n", vertex_file_path);
    char const * VertexSourcePointer = VertexShaderCode.c_str();
    glShaderSource(VertexShaderID, 1, &VertexSourcePointer , NULL);
    glCompileShader(VertexShaderID);

    // Check Vertex Shader
    glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
    glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    std::vector<char> VertexShaderErrorMessage(InfoLogLength);
    glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
    fprintf(stdout, "%s\n", &VertexShaderErrorMessage[0]);

    // Compile Fragment Shader
    printf("Compiling shader : %s\n", fragment_file_path);
    char const * FragmentSourcePointer = FragmentShaderCode.c_str();
    glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , NULL);
    glCompileShader(FragmentShaderID);

    // Check Fragment Shader
    glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
    glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    std::vector<char> FragmentShaderErrorMessage(InfoLogLength);
    glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
    fprintf(stdout, "%s\n", &FragmentShaderErrorMessage[0]);

    // Link the program
    fprintf(stdout, "Linking program\n");
    GLuint ProgramID = glCreateProgram();
    glAttachShader(ProgramID, VertexShaderID);
    glAttachShader(ProgramID, FragmentShaderID);
    glLinkProgram(ProgramID);

    // Check the program
    glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
    glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    std::vector<char> ProgramErrorMessage( std::max(InfoLogLength, int(1)) );
    glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
    fprintf(stdout, "%s\n", &ProgramErrorMessage[0]);

    glDeleteShader(VertexShaderID);
    glDeleteShader(FragmentShaderID);

    return ProgramID;
}

void CGame::Run(int argc, char **argv)
{
    if (!Initialize(argc, argv))
    {
        return;
    }

    // An array of 3 vectors which represents 3 vertices
    static const GLfloat g_vertex_buffer_data[] =
    {
       -1.0f, -1.0f, 0.0f,
       1.0f, -1.0f, 0.0f,
       0.0f,  1.0f, 0.0f,
    };


    // This will identify our vertex buffer
    GLuint vertexbuffer;

    // Generate 1 buffer, put the resulting identifier in vertexbuffer
    glGenBuffers(1, &vertexbuffer);

    // The following commands will talk about our 'vertexbuffer' buffer
    glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);

    // Give our vertices to OpenGL.
    glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);

    GLuint id1 = CGraphicsManager::LoadShader("./Data/shader1.glsl", GL_VERTEX_SHADER);
    GLuint id2 = CGraphicsManager::LoadShader("./Data/shader2.glsl", GL_FRAGMENT_SHADER);
    GLuint programID = glCreateProgram();
    glAttachShader(programID, id1);
    glAttachShader(programID, id2);
    glLinkProgram(programID);
    while(1)
    {
        if (glfwGetKey(GLFW_KEY_ESC) == GLFW_PRESS)
        {
            break;
        }

        glUseProgram(programID);

        // 1rst attribute buffer : vertices
        glEnableVertexAttribArray(0);
        glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
        glVertexAttribPointer(
           0,                  // attribute 0. No particular reason for 0, but must match the layout in the shader.
           3,                  // size
           GL_FLOAT,           // type
           GL_FALSE,           // normalized?
           0,                  // stride
           (void*)0            // array buffer offset
        );

        // Draw the triangle !
        glDrawArrays(GL_TRIANGLES, 0, 3); // Starting from vertex 0; 3 vertices total -> 1 triangle

        glDisableVertexAttribArray(0);

        glfwSwapBuffers();
    }

    Shutdown();
}

/** Initializes all libraries and systems needed for the game.
 *  This does not load any textures, models, scripts, etc. That
 *  is done in each gamestate initialization or execution.
 *  \param Number of arguments in argv.
 *  \param Pointer to C style strings (arguments passed into main)
 *  \return True if all libraries and systems were initialized
 *  successfully, otherwise false.
 */

bool CGame::Initialize(int argc, char **argv)
{
    // Init Utilities System

    g_Utils = new CUtils();
    g_Utils->InitUtils();
    g_Config = new CConfig();
    g_Config->SetFileName(DATA_RESOURCE_PATH_FILENAME);
    g_Config->Read();

    // Init PhysFS Library

    if (!PHYSFS_init(argv[0]))
    {
        m_ErrorString = "Unable to initialize PhysFS: " + (std::string) PHYSFS_getLastError();
        return false;
    }

    // Mount the game's archive file.

    if (!PHYSFS_mount("./Data/resources.rfzp", NULL, 1))
    {
        m_ErrorString = "Unable to mount ./Data/resources.rfzp: " + (std::string) PHYSFS_getLastError();
        return false;
    }

    // Mount the game's data folder.

    if (!PHYSFS_mount("./Data/", NULL, 1))
    {
        m_ErrorString = "Unable to mount ./Data/: " + (std::string) PHYSFS_getLastError();
        return false;
    }

    // Init GLFW Library

    if (glfwInit() == GL_FALSE)
    {
        m_ErrorString = "Failed to initialize GLFW";
        return false;
    }

    // Init Managers

    // Graphics Manager

    m_GraphicsManager = new CGraphicsManager();

    if (!m_GraphicsManager->Init(APPLICATION_WINDOW_TITLE))
    {
        m_ErrorString = "Failed to create a window";
        return false;
    }

    // Init GLEW library
    // This needs to be done after the creation of the window
    // as GLEW needs an OpenGL context. glewExperimental is
    // a fix for the GLEW library.

    glewExperimental = true;
    GLenum err = glewInit();

    if (err != GLEW_OK)
    {
        m_ErrorString = "Failed to initialize GLEW: " + std::string((const char *) glewGetErrorString(err));
        return false;
    }

    // On some versions of GLEW (even with glewExperimental set
    // to true), it will set INVALID_ENUM. This call is to clear
    // the error if it was there. If it wasn't, no harm done.

    glGetError();

    return true;
}

/** Serves as the counterpart to Initialize. Shuts down all
 *  libraries and systems that were used in the game.
 */

void CGame::Shutdown()
{
    // Close Managers and save configuration values

    if (g_Config->StartWrite())
    {
        m_GraphicsManager->SaveConfigurationValues();
        g_Config->EndWrite();
    }

    delete m_GraphicsManager;

    // Close GLFW Library

    glfwTerminate();

    // Deinit PhysFS Library
    // Note: This deinit function can fail if there is a file open for writing.
    //       Be sure to close all files before shutdown.

    PHYSFS_deinit();

    delete g_Config;
    delete g_Utils;
}
